package com.ouc.eddyAlgorithm.service.impl;

import com.ouc.common.constant.VariableName;
import com.ouc.common.entity.NcData;
import com.ouc.common.entity.NcRecord;
import com.ouc.common.ncRead.NcDao;
import com.ouc.common.utils.ArrayUtils;
import com.ouc.common.utils.MathUtils;
import com.ouc.common.utils.nc.NcUtil;
import com.ouc.eddyAlgorithm.service.EddyProfileService;
import com.ouc.visualization.entity.ProfileData;
import com.ouc.visualization.entity.ProfileLineData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ucar.ma2.InvalidRangeException;
import ucar.nc2.NetcdfFile;
import ucar.nc2.Variable;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ExecutionException;

import static com.ouc.common.constant.NcConstants.*;
import static java.lang.Float.NaN;

@Service
public class EddyProfileServiceImpl implements EddyProfileService {
  /**
   * 垂直角度数
   */
  private static float vertical = 90f;
  /**
   * tan（90）和tan（90f）不一样，所以进行判断如果tan（angle）和tan（90）差10认为就是90度
   */
  private static float deviation = (float) 1E1;
  /**
   * 如果只有一个经度和纬度，找interval会有问题。所以手动判断如果有长度小于2认为只有一个值
   */
  private static int minSize = 2;

  @Autowired
  private NcDao ncDao;

  @Override
  public ProfileData getProfileData(String fileName, String variableName, Float[] lon, Float[] lat) throws IOException, InvalidRangeException {
    float x1 = lon[0], x2 = lon[1], y1 = lat[0], y2 = lat[1];
    float k = MathUtils.getLineSlope(x1, y1, x2, y2, 4);

    //1、获取nc文件层级、经度、纬度数组，赋值profileData.levelArr
    NetcdfFile file = NetcdfFile.open(fileName);
    float[] levelArr = NcUtil.getDataOfSlice(ncDao.getVariable(file, VariableName.LEVEL)).getData1D();
    float[] lonArr = NcUtil.getDataOfSlice(ncDao.getVariable(file, VariableName.LON)).getData1D();
    float[] latArr = NcUtil.getDataOfSlice(ncDao.getVariable(file, VariableName.LAT)).getData1D();
    System.out.println("System.currentTimeMillis()1 = " + System.currentTimeMillis());
    //2、计算经纬度间隔，获取线性函数经纬度坐标，赋值profileData.lonAndLat
    float startLon = lonArr[0], endLon = lonArr[lonArr.length - 1], startLat = latArr[0], endLat = latArr[latArr.length - 1];
    float lonInterval = BigDecimal.valueOf(lonArr[1] - lonArr[0])
        .setScale(4, BigDecimal.ROUND_HALF_UP).floatValue();
    float latInterval = BigDecimal.valueOf(latArr[1] - latArr[0])
        .setScale(2, BigDecimal.ROUND_HALF_UP).floatValue();
    int lonAndLatLength = (int) (Math.abs(x2 - x1) / lonInterval);
    float[][] lonAndLat = new float[lonAndLatLength][2];
    if (x1 < x2) {  //根据起始点与终止点的位置判断间距递增还是递减
      for (int i = 0; i < lonAndLatLength; i++) {
        lonAndLat[i] = new float[]{x1 + i * lonInterval, y1 + i * k * lonInterval};
      }
    } else {
      for (int i = 0; i < lonAndLatLength; i++) {
        lonAndLat[i] = new float[]{x1 - i * lonInterval, y1 - i * k * lonInterval};
      }
    }
    System.out.println("System.currentTimeMillis()3 = " + System.currentTimeMillis());
    /**
     * temp是个二维数组，第一个参数是开始经度和结束经度之间差了多少个格子，长度可变
     * 第二个参数长度是2，分别是经度和纬度的下标（在nc文件中第多少个）
     * 3、计算临时temp[][]数组，存放经纬度对应数组下标i,j
     */
    int[][] temp = new int[lonAndLatLength][2];
    for (int i = 0; i < lonAndLat.length; i++) {
      float tempLon = lonAndLat[i][0], tempLat = lonAndLat[i][1];
      temp[i] = tempLon < startLon || tempLon > endLon || tempLat < startLat || tempLat > endLat
          ? null : new int[]{(int) (MathUtils.subtractOfAbs(tempLon, startLon) / lonInterval), (int) (MathUtils.subtractOfAbs(tempLat, startLat) / latInterval)};
    }
    Variable variable;
    //  如果是流数据需要读取u和v在进行计算
    if (Arrays.toString(VariableName.UFLOW.getVariableName()).contains(variableName) ||
        Arrays.toString(VariableName.VFLOW.getVariableName()).contains(variableName)) {
      Variable variableU = ncDao.getVariable(file, VariableName.UFLOW);
      Variable variableV = ncDao.getVariable(file, VariableName.VFLOW);
      float[][] values = new float[levelArr.length][lonAndLatLength];
      for (int i = 0; i < levelArr.length; i++) {
        float[][] data2DU = NcUtil.getDataOfSlice(variableU, i).getData2D();
        float[][] data2DV = NcUtil.getDataOfSlice(variableV, i).getData2D();
        for (int j = 0; j < lonAndLatLength; j++) {
          values[i][j] = temp[j] == null ? NaN :
              (float) Math.sqrt(Math.pow(data2DU[temp[j][1]][temp[j][0]], 2) + Math.pow(data2DV[temp[j][1]][temp[j][0]], 2));
        }
      }
      return new ProfileData(levelArr, lonAndLat, values);
    }
    //4、获取分层nc数据对象，赋值profileData.values
    variable = ncDao.getVariable(file, variableName);
    long start = System.currentTimeMillis();

    float[][] values = new float[levelArr.length][lonAndLatLength];
    for (int i = 0; i < levelArr.length; i++) {
      float[][] data2D = NcUtil.getDataOfSlice(variable, i).getData2D();
      for (int j = 0; j < lonAndLatLength; j++) {
        values[i][j] = temp[j] == null ? NaN : data2D[temp[j][1]][temp[j][0]];
      }
    }
    //5、返回profileData对象
    return new ProfileData(levelArr, lonAndLat, values);
  }

  @Override
  public ProfileData getProfileData(NetcdfFile ncFile, String variableName, Float[] lon, Float[] lat, Integer[] levels) throws IOException, InvalidRangeException, ExecutionException, InterruptedException {
    ProfileData profileData = new ProfileData();
    float x1 = lon[0], x2 = lon[1], y1 = lat[0], y2 = lat[1];
    float k;
    //  获取层级信息
    Map<Integer, Float> levelMap = ncDao.getLevelMap(ncFile);
    levels = levels == null ? new Integer[]{0, levelMap.size() - 1} : levels;
    float[] levelArr = new float[levels[1] - levels[0] + 1];
    for (int i = 0; i < levelMap.size(); i++) {
      levelArr[i] = levelMap.get(i);
    }
    profileData.setLevelArr(levelArr);
    //  获取所有经度用于和给的经度比较
    float[] lonAllArr = NcUtil.getDataOfSlice(ncDao.getVariable(ncFile, VariableName.LON)).getData1D();
    float[] latAllArr = NcUtil.getDataOfSlice(ncDao.getVariable(ncFile, VariableName.LAT)).getData1D();
    float lonInterval = lonAllArr[1] - lonAllArr[0];
    float latInterval = latAllArr[1] - latAllArr[0];
    float[][] lonAndLat;
    //  如果是一条经度线（认为经度没有变化）
    if (lon[1].equals(lon[0])) {
      lonAndLat = new float[(int) Math.ceil(Math.abs(y2 - y1) / latInterval)][2];
      int latLength = lonAndLat.length;
      //  因为纬度有向上向下，所以必须要考虑怎么变化（是否和nc文件的变化一致）
      if (latInterval * (y2 - y1) < 0) {
        for (int i = 0; i < latLength; i++) {
          lonAndLat[i] = new float[]{x1, y1 - i * latInterval};
        }
      } else {
        for (int i = 0; i < latLength; i++) {
          lonAndLat[i] = new float[]{x1, y1 + i * latInterval};
        }
      }
    }
    // 如果都在一个半球里面就是没有跨过界限的话
    else if (x1 <= x2) {
      int lonAndLatLength = (int) Math.ceil(Math.abs(x2 - x1) / lonInterval);
      lonAndLat = new float[lonAndLatLength][2];
      k = MathUtils.getLineSlope(x1, y1, x2, y2, 4);
      for (int i = 0; i < lonAndLatLength; i++) {
        lonAndLat[i] = new float[]{x1 + i * lonInterval, y1 + i * k * lonInterval};
      }
    }
    //  如果已经跨过了界限，要考虑数组拼接
    else {
      k = MathUtils.getLineSlope(lonAllArr[0] + x1, y1, lonAllArr[lonAllArr.length - 1] + x2, y2, 4);
      float[] leftArr = new float[Math.round(Math.abs((lonAllArr[lonAllArr.length - 1] - x1) / lonInterval)) + 1];
      float[] rightArr = new float[Math.round((int) Math.abs((x2 - lonAllArr[0]) / lonInterval)) + 1];
      int lonAndLatLength = rightArr.length + leftArr.length;
      lonAndLat = new float[lonAndLatLength][2];
      int index = 0;
      for (int i = 0; i < leftArr.length + rightArr.length; i++) {
        if (i < leftArr.length) {
          lonAndLat[i] = new float[]{x1 + i * lonInterval, y1 + i * k * lonInterval};
        } else {
          lonAndLat[i] = new float[]{lonAllArr[0] + index * lonInterval, y1 + i * k * lonInterval};
          index++;
        }
      }
    }
    profileData.setLonAndLat(lonAndLat);
    //  如果不是流数据
    if (!variableName.equals(CURRENTUV)) {
      //Map<String, NcData> ncDataOfOneDimensional = ncDao.getNcDataOfOneDimensional(ncFile);
      Map<String, NcData> ncDataWithLimit = ncDao.getNcDataOfLimitFromVariable(ncFile, variableName, lon, lat);
      float[] lonLimitArr = ncDataWithLimit.get(ncDao.getVariable(ncFile, VariableName.LON).getShortName()).getData1D();
      float[] latLimitArr = ncDataWithLimit.get(ncDao.getVariable(ncFile, VariableName.LAT).getShortName()).getData1D();
      //  获取有经纬度限制的三维数据
      NcRecord ncRecord = ncDao.getData3DWithLimit(ncFile, variableName, lon, lat);
      float[][] values = getProfileValue(lonAndLat, ncRecord.getData3D(), lonLimitArr, latLimitArr);
      profileData.setValues(values);
    } else {
      Map<String, NcData> ncDataWithLimitU = ncDao.getNcDataOfLimitFromVariable(ncFile, UU, lon, lat);
      float[] lonLimitArr = ncDataWithLimitU.get(ncDao.getVariable(ncFile, VariableName.LON).getShortName()).getData1D();
      float[] latLimitArr = ncDataWithLimitU.get(ncDao.getVariable(ncFile, VariableName.LAT).getShortName()).getData1D();
      NcRecord ncRecordU = ncDao.getData3DWithLimit(ncFile, UU, lon, lat);
      NcRecord ncRecordV = ncDao.getData3DWithLimit(ncFile, VV, lon, lat);
      float[][][] data3DU = ncRecordU.getData3D();
      float[][][] data3DV = ncRecordV.getData3D();
      float[][][] data = new float[data3DU.length][data3DU[0].length][data3DU[0][0].length];
      for (int j = 0; j < data3DU.length; j++) {
        data[j] = ArrayUtils.getDataFromUV(data3DU[j], data3DV[j]);
      }
      float[][] values = getProfileValue(lonAndLat, data, lonLimitArr, latLimitArr);
      profileData.setValues(values);
    }
    return profileData;
  }


  @Override
  public ProfileData getEddyProfileData(NetcdfFile ncFile, String variableName, Float lon, Float lat, Float lonDiffer, Float latDiffer, Float degree, boolean radianFlag) throws IOException, InvalidRangeException {
    //  获取k
    float k = (float) (radianFlag ? Math.tan(degree) : Math.tan(Math.toRadians(degree)));
    ProfileData profileData = new ProfileData();
    float[] lonAllArr = NcUtil.getDataOfSlice(ncDao.getVariable(ncFile, VariableName.LON)).getData1D();
    float[] latAllArr = NcUtil.getDataOfSlice(ncDao.getVariable(ncFile, VariableName.LAT)).getData1D();
    float latInterval = latAllArr[1] - latAllArr[0];
    float lonInterval = lonAllArr[1] - lonAllArr[0];
    //  设置深度数组
    float[] levelArr = NcUtil.getDataOfSlice(ncDao.getVariable(ncFile, VariableName.LEVEL)).getData1D();
    profileData.setLevelArr(levelArr);
    /*设置偏移*/
    Float[] lonRange = new Float[]{lon - lonDiffer, lon + lonDiffer};
    Float[] latRange = new Float[]{lat - latDiffer, lat + latDiffer};
    //  如果经度纬度越界重新设置
    if (lonRange[0] < lonAllArr[0]) {
      lonRange[0] = lonAllArr[0];
    }
    if (lonRange[1] > lonAllArr[lonAllArr.length - 1]) {
      lonRange[1] = lonAllArr[lonAllArr.length - 1];
    }
    latRange[0] = MathUtils.middle(latRange[0], latAllArr[0], latAllArr[latAllArr.length - 1] - latDiffer);
    latRange[1] = MathUtils.middle(latRange[1], latAllArr[0] + latDiffer, latAllArr[latAllArr.length - 1]);
    float[][] lonAndLat;
    if (Math.abs((float) Math.tan(Math.toRadians(vertical)) - k) < deviation) {
      lonAndLat = new float[(int) (Math.abs(latRange[1] - latRange[0]) / latInterval)][2];
      int latLength = lonAndLat.length;
      for (int i = 0; i < latLength; i++) {
        lonAndLat[i] = new float[]{lon, latRange[0] + i * latInterval};
      }
    }
    /*如果存在斜率*/
    else {
      /*如果二维数组不需要拼接*/
      if (lonRange[0] < lonRange[1]) {
       /* lonAndLat = new float[(int) Math.abs((lonRange[1] - lonRange[0]) / lonInterval)][2];
        for (int i = 0; i < lonAndLat.length; i++) {
          lonAndLat[i] = new float[]{lonRange[0] + i * lonInterval, latRange[0] + i * k * (lonInterval)};
        }*/
        lonAndLat = getEddyLonAndLat(lon, lat, lonRange[0], lonRange[1], latRange[0], latRange[1], k, lonInterval);
      }
      /*认为二维数组需要进行拼接*/
      else {
        float[] leftArr = new float[Math.round(Math.abs((lonAllArr[lonAllArr.length - 1] - lonRange[0]) / lonInterval)) + 1];
        float[] rightArr = new float[Math.round((int) Math.abs((lonRange[1] - lonAllArr[0]) / lonInterval)) + 1];
        int lonAndLatLength = rightArr.length + leftArr.length;
        lonAndLat = new float[lonAndLatLength][2];
        int index = 0;
        for (int i = 0; i < leftArr.length + rightArr.length; i++) {
          if (i < leftArr.length) {
            lonAndLat[i] = new float[]{lonRange[0] + i * lonInterval, latRange[0] + i * k * lonInterval};
          } else {
            lonAndLat[i] = new float[]{lonAllArr[0] + index * lonInterval, latRange[0] + i * k * lonInterval};
            index++;
          }
        }
      }

    }
    profileData.setLonAndLat(lonAndLat);
    //  如果不是流数据
    if (!CURRENTUV.equals(variableName)) {
      //  获取三维数据
      NcRecord ncRecord = ncDao.getData3DWithLimit(ncFile, variableName, lonRange, latRange);
      //  获取三位数组
      float[][][] data3D = ncRecord.getData3D();
      Map<String, NcData> ncDataWithLimit = ncDao.getNcDataOfLimitFromVariable(ncFile, variableName, lonRange, latRange);
      float[] lonLimitArr = ncDataWithLimit.get(ncDao.getVariable(ncFile, VariableName.LON).getShortName()).getData1D();
      float[] latLimitArr = ncDataWithLimit.get(ncDao.getVariable(ncFile, VariableName.LAT).getShortName()).getData1D();
      float[][] values = getProfileValue(lonAndLat, data3D, lonLimitArr, latLimitArr);
      profileData.setValues(values);
    } else {
      Map<String, NcData> ncDataWithLimitU = ncDao.getNcDataOfLimitFromVariable(ncFile, UU, lonRange, latRange);
      float[] lonLimitArr = ncDataWithLimitU.get(ncDao.getVariable(ncFile, VariableName.LON).getShortName()).getData1D();
      float[] latLimitArr = ncDataWithLimitU.get(ncDao.getVariable(ncFile, VariableName.LAT).getShortName()).getData1D();
      NcRecord uu = ncDao.getData3DWithLimit(ncFile, UU, lonRange, latRange);
      NcRecord vv = ncDao.getData3DWithLimit(ncFile, VV, lonRange, latRange);
      float[][][] data3DU = uu.getData3D();
      float[][][] data3DV = vv.getData3D();
      float[][][] data3D = new float[data3DU.length][][];
      for (int i = 0; i < data3DU.length; i++) {
        data3D[i] = ArrayUtils.getDataFromUV(data3DU[i], data3DV[i]);
      }
      float[][] values = getProfileValue(lonAndLat, data3D, lonLimitArr, latLimitArr);
      profileData.setValues(values);
    }
    return profileData;
  }

  /**
   * 对于已经找到拼接好的lonAndLat数组
   * 获取三维数组中对应的值
   * 找和经纬度间隔差一半的，
   * 这个点靠近哪边就认为是这一处的点
   * 返回数据value值
   *
   * @param lonAndLat    已经找到拼接好的数组
   * @param data         三维数据
   * @param lonWithLimit 三维数据中的经度限制数组
   * @param latWithLimit 三维数据中的纬度限制数组
   * @return
   */
  private float[][] getProfileValue(float[][] lonAndLat, float[][][] data, float[] lonWithLimit, float[] latWithLimit) {
    float[][] values = new float[data.length][lonAndLat.length];
    int[][] indexArr = new int[lonAndLat.length][2];
    /*
     * 如果lonWithLimit或者latWithLimit长度不足以获取interval，则手动设置interval为0
     * 主要为了获取索引值，索引值又需要两个数相减*/
    if (lonWithLimit.length < minSize) {
      lonWithLimit = new float[]{lonWithLimit[0], lonWithLimit[0]};
    }
    if (latWithLimit.length < minSize) {
      latWithLimit = new float[]{latWithLimit[0], latWithLimit[0]};
    }
    for (int i = 0; i < lonAndLat.length; i++) {
      /*int arrayIndexFormArr = ArrayUtils.getArrayIndexFormArr(lonWithLimit, lonAndLat[i][0], (float) Math.abs(Arith.div(Arith.sub(lonWithLimit[0], lonWithLimit[1]), 2)));
      int arrayIndexFormArr1 = ArrayUtils.getArrayIndexFormArr(latWithLimit, lonAndLat[i][1], (float) Math.abs(Arith.div(Arith.sub(latWithLimit[0], latWithLimit[1]), 2)));*/
      indexArr[i] = new int[]{ArrayUtils.getArrayIndexFormArr(lonWithLimit, lonAndLat[i][0]),
          ArrayUtils.getArrayIndexFormArr(latWithLimit, lonAndLat[i][1])};
    }
    for (int i = 0; i < values.length; i++) {
      for (int j = 0; j < indexArr.length; j++) {
        values[i][j] = data[i][indexArr[j][1]][indexArr[j][0]];
      }
    }
    return values;
  }

  /**
   * 获取涡旋的LonAndLat数组
   * 从中心点向两边找防止不过涡旋点
   *
   * @param centerLon   中心经度
   * @param centerLat   中心纬度
   * @param startLon    开始经度
   * @param endLon      结束纬度
   * @param startLat    开始纬度
   * @param endLat      结束纬度
   * @param k           斜率
   * @param lonInterval 经度间隔
   * @return
   */
  private float[][] getEddyLonAndLat(float centerLon, float centerLat, float startLon, float endLon, float startLat, float endLat, float k, float lonInterval) {
    float[][] top = new float[Math.round(Math.abs((endLon - centerLon) / lonInterval))][];
    for (int i = 0; i < top.length; i++) {
      top[i] = new float[]{centerLon + i * lonInterval, centerLat + i * k * lonInterval};
      if (top[i][1] > endLat) {
        top[i][1] = endLat;
      } else if (top[i][1] < startLat) {
        top[i][1] = startLat;
      }
    }
    float[][] bottom = new float[Math.round(Math.abs((centerLon - startLon) / lonInterval))][];
    centerLon = centerLon - lonInterval;
    for (int i = 0; i < bottom.length; i++) {
      bottom[i] = new float[]{centerLon - i * lonInterval, centerLat - i * k * lonInterval};
      if (bottom[i][1] > endLat) {
        bottom[i][1] = endLat;
      } else if (bottom[i][1] < startLat) {
        bottom[i][1] = startLat;
      }
    }
    float[][] bottomReverse = ArrayUtils.arrReverse(bottom);
    return ArrayUtils.arrContactByVertical(bottomReverse, top);
  }

  @Override
  public ProfileLineData getProfileLineData(String fileName, String variableName, Float lon, Float lat) throws IOException, InvalidRangeException {
    NetcdfFile file = NetcdfFile.open(fileName);
    float[] levelArr = NcUtil.getDataOfSlice(ncDao.getVariable(file, VariableName.LEVEL)).getData1D();
    float[] values = new float[levelArr.length];
    float[][][] data3D = ncDao.getData3DWithLimit(file, variableName, new Float[]{lon, lon}, new Float[]{lat, lat}).getData3D();
    int index = 0;
    /*获取廓线，三维数组长度：层级 * 1 * 1
     * 可以直接遍历获取
     * */
    for (float[][] floats : data3D) {
      for (float[] aFloat : floats) {
        for (float v : aFloat) {
          values[index++] = v;
        }
      }
    }
    return new ProfileLineData(lon, lat, levelArr, values);
  }
}


